/**
 * 
 */
package jmine.tec.test.junit3;

import junit.framework.Assert;

/**
 * Utility class to assert values with relative precision - relative to the size of the numbers.
 * 
 * @author takeshi
 * @version 08/08/2007
 */
public final class RelativePrecisionAssert {

    public static final double DEFAULT_PRECISION = 1e-14;

    /**
     * C'tor
     */
    private RelativePrecisionAssert() {
        // vazio
    }

    /**
     * Asserts relative value, reporting the given message and using the given precision.
     * 
     * @param message String
     * @param expected double
     * @param actual double
     * @param precision double
     */
    public static void assertRelativeDoubleEquals(final String message, final double expected, final double actual, final double precision) {
        if (!testInfinity(expected, actual)) {
            Assert.fail(message + "\nExpected: " + expected + " but was: " + actual + " with error: " + precision);
        }
        if (!testNaN(actual, expected)) {
            Assert.fail(message + "\nExpected: " + expected + " but was: " + actual + " with error: " + precision);
        }
        final double diff = Math.abs(Math.abs(expected) - Math.abs(actual));
        final double toCheck = Math.max(Math.abs(diff / expected), Math.abs(diff / actual));
        if (toCheck > precision) {
            Assert.fail(message + "\nExpected: " + expected + " but was: " + actual + " with error: " + precision);
        }
    }

    /**
     * Testa se ambos sao infinitos com o mesmo sinal ou se nenhum eh infinito
     * 
     * @param expected the first value
     * @param actual the second value
     * @return boolean
     */
    private static boolean testInfinity(final double expected, final double actual) {
        if (Double.isInfinite(actual)) {
            return Double.isInfinite(expected) && expected * actual > 0;
        }
        if (Double.isInfinite(expected)) {
            return Double.isInfinite(actual) && expected * actual > 0;
        }
        return true;
    }

    /**
     * Testa se ambos sao NaN ou nenhum deles é
     * 
     * @param first the first value
     * @param second the second value
     * @return boolean
     */
    private static boolean testNaN(final double first, final double second) {
        if (Double.isNaN(first)) {
            return Double.isNaN(second);
        }
        if (Double.isNaN(second)) {
            return Double.isNaN(first);
        }
        return true;
    }

    /**
     * Asserts relative double value. Calls the full variant of this method with default precision.
     * 
     * @see #assertRelativeDoubleEquals(String, double, double, double)
     * @see #DEFAULT_PRECISION
     * @param message the message to show
     * @param expected expected value
     * @param actual actual value
     */
    public static void assertRelativeDoubleEquals(final String message, final double expected, final double actual) {
        assertRelativeDoubleEquals(message, expected, actual, DEFAULT_PRECISION);
    }

    /**
     * Asserts relative double value with no message.
     * 
     * @see #assertRelativeDoubleEquals(String, double, double, double)
     * @param expected the expected value
     * @param actual the actual value
     * @param precision the precision to use. 0 (zero) means exact value.
     */
    public static void assertRelativeDoubleEquals(final double expected, final double actual, final double precision) {
        assertRelativeDoubleEquals("", expected, actual, precision);
    }

    /**
     * Asserts relative double value with no message and default precision.
     * 
     * @see #assertRelativeDoubleEquals(String, double, double, double)
     * @see #DEFAULT_PRECISION
     * @param expected the expected value
     * @param actual the acutal value
     */
    public static void assertRelativeDoubleEquals(final double expected, final double actual) {
        assertRelativeDoubleEquals("", expected, actual, DEFAULT_PRECISION);
    }

}
